﻿using PIMS.Interface;
using PIMS.XmlModel.Snap7;
using PIMS.XmlModel;
using S7.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using PIMS_Interface.XmlModel.S7NETPlus;
using NPOI.SS.Formula.Functions;
using System.Windows.Forms;
using System.Linq.Expressions;
using S7.Net.Types;

namespace PIMS.Commication.S7NetPlus
{
    /// <summary>
    /// v1.0 nuget S7netplus@0.13.0
    /// </summary>
    public class S7NetPlusHelper : ICommunication
    {
        Plc s7netClient;

        private DBInfo dbi;
        private string name = "";
        private string _ip = "";

        public string IP
        {
            get
            {

                return this._ip;
            }
            set
            {
                this._ip = value;
            }
        }

        /// <summary>
        /// PLC在线
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return s7netClient.IsConnected;
            }
        }

        public IPLCXModel xModel { get; set; }

        public S7NetPlusHelper(string xmlpath)
        {
            short slot = 1;
            try
            {
                XmlDocument xmlDocument = new XmlDocument();
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.IgnoreComments = true;
                XmlReader reader = XmlReader.Create(xmlpath, settings);
                xmlDocument.Load(reader);

                XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Device");
                XmlNodeList nodePLC = xmlDocument.GetElementsByTagName("PLC");
                reader.Close();

                this.IP = nodeList[0].Attributes["Ip"].Value;
                this.name = nodeList[0].Attributes["deviceName"].Value;

                string type = nodeList[0].Attributes["Type"].Value;
                if (type.ToUpper() == "S7-300")
                {
                    slot = 2;
                }
                else if (type.ToUpper() == "S7-400")
                {
                    slot = 3;
                }

                int num = int.Parse(nodePLC[0].Attributes["Num"].Value);

                dbi.id = new string[num];
                dbi.db = new string[num];
                dbi.addr = new string[num];
                dbi.name = new string[num];

                int i = 0;
                foreach (XmlNode itemID in nodePLC[0].ChildNodes)
                {
                    if (i >= num)
                        break;
                    dbi.id[i] = itemID.Attributes["id"].Value;
                    dbi.db[i] = itemID.Attributes["db"].Value;
                    dbi.addr[i] = itemID.Attributes["addr"].Value;
                    dbi.name[i] = itemID.Attributes["name"].Value;
                    i++;
                }

                CpuType cpuType = CpuType.S71200;
                switch (type.ToUpper())
                {
                    case "S7-200SMART":
                        cpuType = CpuType.S7200Smart;
                        break;
                    case "S7-300":
                        cpuType = CpuType.S7300;
                        break;
                    case "S7-400":
                        cpuType = CpuType.S7400;
                        break;
                    case "S7-1200":
                        cpuType = CpuType.S71200;
                        break;
                    case "S7-1500":
                        cpuType = CpuType.S71500;
                        break;
                }
                this.s7netClient = new Plc(cpuType, this.IP, 0, slot);
                this.s7netClient.ReadTimeout = 2000;
                this.s7netClient.WriteTimeout = 2000;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public S7NetPlusHelper(S7NETPlusXModel x)
        {
            short slot = 1;
            try
            {
                this.IP = x.Device.Ip;
                this.name = x.Device.deviceName;

                string type = x.Device.Type;
                if (type.ToUpper() == "S7-300")
                {
                    slot = 2;
                }
                else if (type.ToUpper() == "S7-400")
                {
                    slot = 3;
                }

                int num = x.PLC.Num;

                dbi.id = new string[num];
                dbi.db = new string[num];
                dbi.addr = new string[num];
                dbi.name = new string[num];

                int i = 0;
                foreach (XItemsID itemID in x.PLC.itemsID)
                {
                    if (i >= num)
                        break;
                    dbi.id[i] = itemID.id.ToString();
                    dbi.db[i] = itemID.db;
                    dbi.addr[i] = itemID.addr;
                    dbi.name[i] = itemID.name;
                    i++;
                }

                CpuType cpuType = CpuType.S71200;
                switch (type.ToUpper())
                {
                    case "S7-200SMART":
                        cpuType = CpuType.S7200Smart;
                        break;
                    case "S7-300":
                        cpuType = CpuType.S7300;
                        break;
                    case "S7-400":
                        cpuType = CpuType.S7400;
                        break;
                    case "S7-1200":
                        cpuType = CpuType.S71200;
                        break;
                    case "S7-1500":
                        cpuType = CpuType.S71500;
                        break;
                }
                this.s7netClient = new Plc(cpuType, this.IP, 0, slot);
                this.s7netClient.ReadTimeout = 2000;
                this.s7netClient.WriteTimeout = 2000;
            }
            catch (Exception ex)
            {
                //System.Windows.Forms.MessageBox.Show(ex.Message);
                throw ex;
            }
        }

        public void Connect()
        {
            try
            {
                s7netClient.Open();
            }
            catch
            {
            }
        }

        public void DisConnect()
        {
            s7netClient.Close();
        }

        public string GetItemName(int itemid)
        {
            return dbi.name[itemid];
        }

        public string GetDeviceName()
        {
            return this.name;
        }

        public string GetIPAddr()
        {
            return this.IP;
        }


        /// <summary>
        /// 泛型读
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="itemid"></param>
        /// <param name="result">读取结果（成功或失败）</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public T Read<T>(int itemid, out bool result)
        {
            if (dbi.id == null)
                throw new Exception("PLC XML配置未初始化！");
            int startAddr;
            int bit;
            string type = this.GetType(dbi.addr[itemid], out startAddr, out bit);
            int dbNum = this.GetDBNum(dbi.db[itemid]);

            result = false;
            T ret = default(T);
            object obj = null;
            string typeName = typeof(T).Name;
            switch (typeName)
            {
                case "Boolean":
                    if (s7netClient == null) return (T)obj;
                    lock (this)
                    {
                        obj = s7netClient.Read(dbi.db[itemid] + "." + dbi.addr[itemid]);
                    }
                    result = obj != null;
                    ret = (T)obj;
                    break;
                case "Byte":
                    lock (this)
                    {
                        obj = s7netClient.Read(dbi.db[itemid] + "." + dbi.addr[itemid]);
                    }
                    result = obj != null;
                    ret = (T)obj;
                    break;
                case "Byte[]":
                    byte[] myarray = null;
                    lock (this)
                    {
                        myarray = s7netClient.ReadBytes(DataType.DataBlock, dbNum, startAddr, bit);
                    }                        
                    result = myarray != null;
                    ret = (T)(Object)myarray;
                    break;
                case "Int16":
                case "Int32":
                    lock (this)
                    {
                        obj = s7netClient.Read(dbi.db[itemid] + "." + dbi.addr[itemid]);
                    }                        
                    result = obj != null;
                    ret = (T)Convert.ChangeType(obj, typeof(T));
                    break;
                case "String":
                    if (type == "wstring")
                    {
                        lock (this)
                        {
                            obj = s7netClient.Read(DataType.DataBlock, dbNum, startAddr, VarType.S7WString, bit);
                        }                           
                    }
                    else
                    {
                        byte[] strarray = null;
                        lock (this)
                        {
                            strarray = s7netClient.ReadBytes(DataType.DataBlock, dbNum, startAddr, bit + 2);
                        }                           
                        byte[] rev = new byte[strarray[1]];
                        Array.Copy(strarray, 2, rev, 0, rev.Length);
                        obj = Encoding.ASCII.GetString(rev);
                    }
                    result = obj != null;
                    ret = (T)(Object)obj;
                    break;
                case "Single":
                    lock (this)
                    {
                        obj = s7netClient.Read(dbi.db[itemid] + "." + dbi.addr[itemid]);
                    }                       
                    result = obj != null;
                    ret = (T)(Object)(((uint)obj).ConvertToFloat());
                    break;
                default:
                    throw new Exception(string.Format("类型名:{0}不支持！", typeName));
            }
            return ret;
        }

        /// <summary>
        /// 泛型写
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="itemid"></param>
        /// <param name="value"></param>
        /// <returns>写入结果（成功或失败）</returns>
        /// <exception cref="Exception"></exception>
        public bool Write<T>(int itemid, T value)
        {
            if (dbi.id == null)
                throw new Exception("PLC XML配置未初始化！");
            int startAddr;
            int bit;
            string type = this.GetType(dbi.addr[itemid], out startAddr, out bit);
            int dbNum = this.GetDBNum(dbi.db[itemid]);

            bool ret = false;
            string typeName = typeof(T).Name;
            switch (typeName)
            {
                case "Boolean":
                    lock (this)
                    {
                        s7netClient.Write(dbi.db[itemid] + "." + dbi.addr[itemid], value);
                    }                       
                    ret = true;
                    break;
                case "Byte":
                    //使用时需要赋值给一个Byte变量，再写
                    lock (this)
                    {
                        s7netClient.Write(dbi.db[itemid] + "." + dbi.addr[itemid], value);
                    }                        
                    ret = true;
                    break;
                case "Byte[]":
                    lock (this)
                    {
                        s7netClient.WriteBytes(DataType.DataBlock, dbNum, startAddr, (byte[])(object)value);
                    }                        
                    ret = true;
                    break;
                case "Int16":
                case "Int32":
                    lock (this)
                    {
                        s7netClient.Write(dbi.db[itemid] + "." + dbi.addr[itemid], Convert.ToInt16(value.ToString()));
                    }                       
                    ret = true;
                    break;
                case "String":
                    if (type == "wstring")
                    {
                        byte[] buff = Encoding.Unicode.GetBytes(value.ToString());
                        byte[] buff2 = new byte[buff.Length + 2];
                        buff2[0] = (byte)(value.ToString().Length / 255);
                        buff2[1] = (byte)(value.ToString().Length % 255);
                        Array.Copy(buff, 0, buff2, 2, buff.Length);
                        //Unicode高低位颠倒
                        for (int i = 2; i < buff2.Length - 1; i += 2)
                        {
                            var temp = buff2[i];
                            buff2[i] = buff2[i + 1];
                            buff2[i + 1] = temp;
                        }
                        lock (this)
                        {
                            s7netClient.Write(DataType.DataBlock, dbNum, startAddr + 2, buff2);
                        }                            
                    }
                    else
                    {
                        byte[] buff = Encoding.UTF8.GetBytes(value.ToString());
                        byte[] buff2 = new byte[bit + 1];
                        buff2[0] = (byte)(buff.Length % 255);
                        int length = buff.Length;
                        if (length > bit) length = bit;
                        Array.Copy(buff, 0, buff2, 1, length);
                        lock (this)
                        {
                            s7netClient.Write(DataType.DataBlock, dbNum, startAddr + 1, buff2);
                        }                           
                    }
                    ret = true;
                    break;
                case "Single":
                    lock (this)
                    {
                        s7netClient.Write(DataType.DataBlock, dbNum, startAddr, value);
                    }                       
                    ret = true;
                    break;
                default:
                    throw new Exception(string.Format("类型名:{0}不支持！", typeName));
            }
            return ret;
        }

        /// <summary>
        /// 更新 支持定长STRING
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="startAddr"></param>
        /// <param name="bit"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private string GetType(string addr, out int startAddr, out int bit)
        {
            string Addr = addr.ToUpper();
            bit = 0;
            if (Addr.StartsWith("DBW"))
            {
                #region
                Addr = Addr.Substring(3);
                if (Addr.Contains(","))
                {
                    //Array
                    string[] strs = Addr.Split(',');
                    if (strs.Length == 2)
                    {
                        int.TryParse(strs[0], out startAddr);
                        int.TryParse(strs[1], out bit);
                    }
                    else
                    {
                        throw new Exception("PLC XML配置有误，地址：" + addr);
                    }
                }
                else
                {
                    int.TryParse(Addr, out startAddr);
                }
                return "int";
                #endregion
            }
            else if (Addr.StartsWith("DBSST"))
            {
                #region
                if (Addr.Contains("."))
                {
                    string str = Addr.Substring(5);
                    int.TryParse(str.Split('.')[0], out startAddr);
                    int.TryParse(str.Split('.')[1], out bit);
                }
                else
                {
                    int.TryParse(Addr.Substring(5), out startAddr);
                    bit = 254;
                }
                return "string";
                #endregion
            }
            else if (Addr.StartsWith("DBSSWT"))
            {
                #region
                if (Addr.Contains("."))
                {
                    string str = Addr.Substring(6);
                    int.TryParse(str.Split('.')[0], out startAddr);
                    int.TryParse(str.Split('.')[1], out bit);
                }
                else
                {
                    int.TryParse(Addr.Substring(6), out startAddr);
                    bit = 254;
                }
                return "wstring";
                #endregion
            }
            else if (Addr.StartsWith("DBX"))
            {
                #region
                Addr = Addr.Substring(3);
                if (Addr.Contains("."))
                {
                    string[] strs = Addr.Split('.');
                    if (strs.Length == 2)
                    {
                        int.TryParse(strs[0], out startAddr);
                        int.TryParse(strs[1], out bit);
                    }
                    else
                    {
                        throw new Exception("PLC XML配置有误，地址：" + addr);
                    }
                }
                else
                {
                    int.TryParse(Addr, out startAddr);
                }
                return "bool";
                #endregion
            }
            else if (Addr.StartsWith("DBB"))
            {
                #region
                Addr = Addr.Substring(3);
                if (Addr.Contains(","))
                {
                    //Array
                    string[] strs = Addr.Split(',');
                    if (strs.Length == 2)
                    {
                        int.TryParse(strs[0], out startAddr);
                        int.TryParse(strs[1], out bit);
                    }
                    else
                    {
                        throw new Exception("PLC XML配置有误，地址：" + addr);
                    }
                }
                else
                {
                    int.TryParse(Addr, out startAddr);
                }
                return "byte";
                #endregion
            }
            else if (Addr.StartsWith("DBD"))
            {
                #region
                int.TryParse(Addr.Substring(3), out startAddr);
                return "real";
                #endregion
            }
            throw new Exception("PLC XML配置有误，地址：" + addr);
        }

        private int GetDBNum(string dbName)
        {
            int dbi = 0;
            if (dbName.ToUpper().StartsWith("DB"))
            {
                int.TryParse(dbName.ToUpper().Substring(2), out dbi);
            }
            return dbi;
        }
    }

    public struct DBInfo
    {
        public string[] id;
        public string[] db;
        public string[] addr;
        public string[] name;
    }
}
